home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Graphics / PostScript / EnhancedYap / Source / PSText.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  10.9 KB  |  457 lines

  1. /*
  2.  *  PSText.m, subclass of Text overriding paste: for adding functionality
  3.  *  Author: Ali Ozer
  4.  *  Created: Mar 22, 89
  5.  *
  6.  *  You may freely copy, distribute and reuse the code in this example.
  7.  *  NeXT disclaims any warranty of any kind, expressed or implied,
  8.  *  as to its fitness for any particular use.
  9.  */
  10.  
  11. #import <appkit/appkit.h>
  12. #import <string.h>
  13. #import <mach/mach.h>
  14. #import "PSText.h"
  15.  
  16. @implementation PSText
  17. #define CONTROL_OFFSET (unsigned short)0x40
  18.  
  19.  
  20. /** Cursor Movement Commands **/
  21.  
  22. #define CTRL_A ('A'  - CONTROL_OFFSET)
  23. #define CTRL_B ('B'  - CONTROL_OFFSET)
  24. #define CTRL_E ('E'  - CONTROL_OFFSET)
  25. #define CTRL_F ('F'  - CONTROL_OFFSET)
  26. #define CTRL_N ('N'  - CONTROL_OFFSET)
  27. #define CTRL_P ('P'  - CONTROL_OFFSET)
  28. #define ALT_LESS ((unsigned short)0xa3)
  29. #define ALT_GREATER ((unsigned short) 0xb3)
  30. #define ALT_B ((unsigned short) 0xe5)
  31. #define ALT_F ((unsigned short) 0xa6)
  32.  
  33. /** Delete Commands  **/
  34.  
  35. #define CTRL_D ('D'  - CONTROL_OFFSET)
  36. #define CTRL_K ('K'  - CONTROL_OFFSET)
  37. #define CTRL_O ('O'  - CONTROL_OFFSET)
  38. #define CTRL_Y ('Y'  - CONTROL_OFFSET)
  39. #define ALT_D ((unsigned short) 0x44)
  40. #define ALT_H ((unsigned short) 0xe3)
  41.  
  42.  
  43.  
  44. typedef struct _sel
  45. {
  46.     unsigned short charCode;
  47.     SEL selector;
  48.     SEL positionSelector;
  49.     char *selectorString;
  50.     char *positionSelectorString;
  51. } SelectorItem;
  52.  
  53. static SelectorItem emacsMetaKeys[] = 
  54. {
  55. {ALT_B, 0, 0, "moveToPosition:", "positionForWordBegin"},
  56. {ALT_F, 0, 0, "moveToPosition:", "positionForWordEnd"},
  57. {ALT_LESS, 0, 0, "moveToPosition:", "positionForDocumentBegin"},
  58. {ALT_GREATER, 0, 0, "moveToPosition:", "positionForDocumentEnd"},
  59. {ALT_D, 0, 0, "deleteToPosition:", "positionForWordEnd"},
  60. {ALT_H, 0, 0, "deleteToPosition:", "positionForWordBegin"},
  61. {0}
  62. };
  63.  
  64. static SelectorItem emacsControlKeys[] = 
  65. {
  66. {CTRL_A, 0, 0, "moveToPosition:", "positionForLineBegin"},
  67. {CTRL_E, 0, 0, "moveToPosition:", "positionForLineEnd"},
  68. {CTRL_K, 0, 0, "deleteToLineEnd", 0},
  69. {CTRL_D, 0, 0, "deleteToPosition:", "nextPositionIfEmpty"},
  70. {CTRL_Y, 0, 0, "yank", 0},
  71. {0}
  72. };
  73.  
  74. unsigned short emacsFilter (unsigned short
  75.     charCode, int flags, unsigned short charSet)
  76. {
  77.     if (flags & NX_CONTROLMASK) 
  78.     {         
  79.     switch(charCode) {
  80.         case CTRL_F:
  81.         return NX_RIGHT;
  82.         case CTRL_B:
  83.             return NX_LEFT;
  84.         case CTRL_N:
  85.             return NX_DOWN;
  86.         case CTRL_P:
  87.             return NX_UP;
  88.         default: break;
  89.     }
  90.     } 
  91.     return NXEditorFilter(charCode, flags, charSet);
  92. }
  93.  
  94.  
  95. int GetPrevious(NXStream *s)
  96. {
  97.      int pos;
  98.      int ch;
  99.      
  100.      pos = NXTell(s);
  101.      if (pos <= 0) return EOF;
  102.      NXSeek(s, --pos, NX_FROMSTART);
  103.      ch = NXGetc(s);
  104.      NXUngetc(s);
  105.      return ch;
  106. }
  107.  
  108. /*
  109.  * The following method overrides the Text paste: method. If there's any
  110.  * PostScript on the pasteboard, this method will paste that in before pasting
  111.  * ASCII. If there's no PostScript on the pasteboard, then this method will
  112.  * simply call the overridden paste: method.
  113.  */
  114. - paste:(id)sender
  115. {
  116.     id pb = [Pasteboard new];  
  117.     char *data;
  118.     char *const *s;  /* We use s to go through types. */
  119.     char *const *types = [pb types];
  120.     int len;
  121.  
  122.     /* Check to see if we have any PostScript on the pasteboard... */
  123.  
  124.     for (s = types; *s; s++) if (!strcmp(*s, NXPostScriptPboard)) break;
  125.  
  126.     /* At this point, if *s != NULL, we found PostScript on the pasteboard. */
  127.  
  128.     if (*s && [pb readType:NXPostScriptPboard data:&data length:&len] && len) {
  129.     [self replaceSel:data length:len];
  130.         vm_deallocate (task_self(), (vm_address_t)data, (vm_size_t)len);     
  131.     } else {
  132.         [super paste:sender];  /* No PS; pass the task on to Text. */
  133.     }
  134.  
  135.     return self;
  136. }
  137.  
  138. // Complete the build of the selector tables
  139. +initialize
  140. {
  141.     SelectorItem *cur;
  142.  
  143.     for (cur = emacsMetaKeys; cur->charCode; cur++)
  144.     {
  145.     cur->selector = sel_getUid(cur->selectorString);
  146.     cur->positionSelector = sel_getUid(cur->positionSelectorString);
  147.     }
  148.  
  149.     for (cur = emacsControlKeys; cur->charCode; cur++)
  150.     {
  151.     cur->selector = sel_getUid(cur->selectorString);
  152.     cur->positionSelector = sel_getUid(cur->positionSelectorString);
  153.     }
  154.     return self;
  155. }
  156.  
  157. - (int)positionForLineBeginActual
  158. /* Not currently in use.  Looks for newline to find actual paragraph begin.
  159.  */
  160. {
  161.     NXStream *s = [self stream];
  162.     int pos;
  163.     int ch;
  164.     
  165.     if (spN.cp < 0) return 0; // Is this the right thing to do here?
  166.  
  167.     NXSeek(s, sp0.cp, NX_FROMSTART);
  168.     while (((ch = GetPrevious(s)) != EOF) && (ch != '\n'));
  169.     pos = NXTell(s);
  170.     if (ch != EOF) pos++;
  171.     return pos;
  172. }
  173.  
  174. - (int)positionForLineEndActual
  175. /* Not currently in use.  Looks for newline to find actual paragraph end.
  176.  */
  177. {
  178.     NXStream *s = [self stream];
  179.  
  180.     int pos;
  181.     int ch;
  182.     int max = [self textLength];
  183.     
  184.     if (spN.cp < 0) return 0; 
  185.     if (spN.cp > max) return max;
  186.  
  187.     NXSeek(s, spN.cp, NX_FROMSTART);
  188.     while (((ch = NXGetc(s)) != EOF) && (ch != '\n'));
  189.     pos = NXTell(s);
  190.     if (ch != EOF) pos--;
  191.     return pos;
  192. }
  193.  
  194. - (int)positionForLineEndVisual
  195. /* This uses the break array to find the visual line end.  
  196.  * However, it subtracts one from the position because of that behavior 
  197.  * of the Text object that makes the position at the end of one line 
  198.  * the same character position a the beginning of next line.  Seems to
  199.  * be no way to position the insertion point at the end of the line.
  200.  * Bummer.
  201.  */
  202. {
  203.     int lineLength;
  204.     int line;
  205.     
  206.     line = (spN.line /sizeof(NXLineDesc));
  207.     lineLength = theBreaks->breaks[line] & 0x3fff;
  208.     lineLength--; // Notice the hack....
  209.     return (spN.c1st + lineLength);
  210. }
  211.  
  212. - (int)positionForLineBeginVisual
  213. {
  214.     return (sp0.c1st);
  215. }
  216.  
  217. /** BIG FAT HAIRY NOTE
  218.  * This is how to change CTRL-A, CTRL-E, CTRL-K to use paragraph ends
  219.  * (actual newlines) instead of visual line breaks.  Have the position
  220.  * for line end methods call to the position for actual rather than
  221.  * visual.
  222.  */
  223.  
  224. - (int)positionForLineBegin
  225. {
  226.     return [self positionForLineBeginVisual];
  227. }
  228.  
  229. - (int)positionForLineEnd
  230. {
  231.     return [self positionForLineEndVisual];
  232.  
  233. }
  234.  
  235. - (int)nextPositionIfEmpty
  236. {
  237.      if (sp0.cp == spN.cp) 
  238.     return spN.cp + 1;
  239.     else
  240.     return spN.cp;
  241. }
  242.  
  243. /* This is my quick decision on what characters count as a word, and which
  244.  * don't.  The correct way to do this is to parse the ClickTable, but the
  245.  * documentation is so incredibly sparse on this one....
  246.  */
  247.  
  248. #define NORMAL_CHAR(ch) (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||((ch >= '0') && (ch <= '9')) || (ch == '\'')|| (ch == '_'))
  249.  
  250.  
  251. - (int)positionForWordEnd
  252. {
  253.     NXStream *s = [self stream];
  254.  
  255.     int pos;
  256.     int ch;
  257.     int max = [self textLength];
  258.     
  259.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  260.     if (spN.cp > max) return max;
  261.  
  262.     NXSeek(s, spN.cp, NX_FROMSTART);
  263.     while ((ch = NXGetc(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  264.     while ((ch = NXGetc(s)) != EOF && NORMAL_CHAR(ch));    // jump normal chars
  265.     pos = NXTell(s);
  266.     if (ch != EOF) pos--;
  267.     return pos;
  268. }
  269.  
  270. - (int)positionForWordBegin
  271. {
  272.     NXStream *s = [self stream];
  273.  
  274.     int pos;
  275.     int ch;
  276.     int max = [self textLength];
  277.     
  278.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  279.     if (spN.cp > max) return max;
  280.  
  281.     NXSeek(s, sp0.cp, NX_FROMSTART);
  282.     while ((ch = GetPrevious(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  283.     while ((ch = GetPrevious(s)) != EOF && NORMAL_CHAR(ch)); // jump normal chars
  284.     pos = NXTell(s);
  285.     if (ch != EOF) pos++;
  286.     return pos;
  287. }
  288.  
  289. - (int) positionForDocumentEnd
  290. {
  291.      return [self textLength];
  292. }
  293.  
  294. - (int) positionForDocumentBegin
  295. {
  296.      return 0;
  297. }
  298.  
  299. - moveToPosition:(SEL)command
  300. {
  301.     int pos;
  302.     
  303.     pos = (int)[self perform:command];
  304.     [self setSel:pos :pos];
  305.     [self scrollSelToVisible];
  306.     return self;
  307. }
  308.  
  309. - deleteToPosition:(SEL)command
  310. /* Entry point for delete forward/backward word
  311.  */
  312. {
  313.     int pos;
  314.     int start,end;
  315.     
  316.     pos = (int)[self perform:command];
  317.     if (pos > spN.cp) 
  318.     {     // if position extends to the right
  319.         start = sp0.cp;
  320.     end = pos;
  321.     } 
  322.     else 
  323.     {        // else position extends to the left
  324.         start = pos;
  325.     end = spN.cp;
  326.     }
  327.     [self delete:start :end];
  328.     return self;
  329. }
  330.  
  331. - delete:(int)start :(int)end
  332. /* Entry point for all deletes done for Emacs bindings.  Turns off 
  333.  * autodisplay to avoid flicker and other unwanted drawing artifacts.
  334.  * Calls cut and uses the Pasteboard to implement yank.  It is possible
  335.  * to implement separate Emacs kill buffer, but it would be a bit of
  336.  * hassle, because you need a Change object to keep both the runs and
  337.  * the text that is yanked.  You can do it quick by storing only ASCII
  338.  * text, which is not a good idea.  (Actually, to be correct, this is
  339.  * all that Edit does, but who wants to use Edit for a role model?)
  340.  */
  341. {
  342.     if (end - start) 
  343.     {
  344.     [self setAutodisplay:NO];
  345.     [self setSel:start :end];
  346.     [self cut:self];
  347.     [[self setAutodisplay:YES] display];
  348.     }
  349.     return self;
  350. }
  351.  
  352.  
  353. - deleteToLineEnd
  354. /* Somewhat icky hack has to handle the special case for deleting at end 
  355.  * of line.  If in middle of line, don't delete the new line.  If at the 
  356.  * very end of the line, do delete the new line.
  357.  */
  358. {
  359.     int pos;
  360.     int start,end;
  361.     
  362.     pos = [self positionForLineEnd];
  363.     start = sp0.cp;
  364.     end = pos;
  365.     if (start == end) {// If already at end of line
  366.     int line;
  367.     int endsWithNewLine;
  368.     
  369.     line = (spN.line /sizeof(NXLineDesc));
  370.     endsWithNewLine = theBreaks->breaks[line] & 0x4000;
  371.  
  372.     if (endsWithNewLine) 
  373.         end++;
  374.     else  // Bail on case where at visual end of line, but no newline
  375.         return self;
  376.     }
  377.     [self delete:start :end];
  378.     return self;
  379. }
  380.  
  381.  
  382. - yank
  383. {
  384.     [self paste:self];
  385.     return self;
  386. }
  387.  
  388.  
  389. - (BOOL) emacsEvent:(NXEvent *)event
  390. {
  391.     SelectorItem *cur;
  392.     unsigned charCode = event->data.key.charCode;
  393.     
  394.     if (event->flags & NX_CONTROLMASK) 
  395.     {  
  396.         cur = emacsControlKeys;
  397.             
  398.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  399.     if (cur->charCode) 
  400.     {
  401.         [self perform:cur->selector 
  402.             withSel:(cur->positionSelector? cur->positionSelector : 0)];
  403.         return YES;
  404.     }
  405.     }
  406.     if (event->flags & NX_ALTERNATEMASK) 
  407.     {  
  408.         cur = emacsMetaKeys;
  409.             
  410.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  411.     if (cur->charCode) 
  412.     {
  413.         [self perform:cur->selector 
  414.             withSel:(cur->positionSelector? cur->positionSelector : 0)];
  415.         return YES;
  416.     }
  417.     }
  418.     return NO;
  419. }
  420.  
  421. - keyDown:(NXEvent *)event
  422. {
  423.     if ([self emacsEvent:event]) 
  424.     return self;
  425.     else
  426.     return [super keyDown:event];
  427. }
  428.  
  429. - (int)perform:(SEL)selector withSel:(SEL)helper 
  430. {
  431.     int   (*func)(id,SEL,SEL); 
  432.     
  433.     func = (int (*)(id,SEL,SEL))[self methodFor:selector];
  434.     return (* func)(self, selector, helper);
  435. }
  436.  
  437. - initFrame:(NXRect *)fRect
  438. {
  439.     NXRect r = *fRect;
  440.     [super initFrame:fRect];
  441.     [self setMonoFont:YES];
  442.     [self setSelFontSize:12];
  443.     [self setBackgroundGray:NX_WHITE];
  444.     [self setOpaque:YES];
  445.     [self setCharFilter:(NXCharFilterFunc)emacsFilter];
  446.     [self notifyAncestorWhenFrameChanged: YES];
  447.     [self setVertResizable:YES];
  448.     [self setMinSize:&r.size];
  449.     r.size.height = 1.0e30;
  450.     [self setMaxSize:&r.size];
  451.     return self;
  452. }
  453.  
  454.  
  455. @end
  456.  
  457.